package editor.animation;

import animation.*;
import com.crunch.math.Vector2f;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

public class MainPanel extends JFrame {
	private final AnimationEditor editor;

	private JTabbedPane tabs;
	private EditorPanel[] panels;

	public MainPanel(AnimationEditor editor) {
		super();
		this.editor = editor;

		// a hack to account for border sizes
		Insets insets = getInsets();
		setMinimumSize(new Dimension(512 + insets.left + insets.right, getMinimumSize().height));
		setSize(512 + insets.left + insets.right, 768 + insets.top + insets.bottom);

		// create controls
		JMenuBar menuBar = new JMenuBar();

		JMenu fileMenu = new JMenu("File");
		menuBar.add(fileMenu);

		JMenuItem newMenuItem = new JMenuItem("New character");
		newMenuItem.setMnemonic(KeyEvent.VK_N);
		newMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK));
		fileMenu.add(newMenuItem);
		newMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (MainPanel.this.editor) {
					int result = JOptionPane.showConfirmDialog(MainPanel.this, "Save before closing?");
					if (result == JOptionPane.CANCEL_OPTION) {
						// do nothing
					} else if (result == JOptionPane.NO_OPTION) {
						startNewCharacter();
					} else if (result == JOptionPane.YES_OPTION) {
						boolean saved = false;
						if (MainPanel.this.editor.characterPath == null) {
							String pathStr = chooseCharacterFolder();
							if (pathStr == null) {
								// do nothing
							} else {
								// try saving
								Path path = FileSystems.getDefault().getPath(pathStr);
								saved = save(path);
							}
						} else {
							// try saving
							saved = save(MainPanel.this.editor.characterPath);
						}
						if (saved) {
							startNewCharacter();
						}
					}
				}
			}
		});

		JMenuItem openMenuItem = new JMenuItem("Open character");
		openMenuItem.setMnemonic(KeyEvent.VK_O);
		openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
		fileMenu.add(openMenuItem);
		openMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (MainPanel.this.editor) {
					int result = JOptionPane.showConfirmDialog(MainPanel.this, "Save before closing?");
					if (result == JOptionPane.CANCEL_OPTION) {
						// do nothing
					} else if (result == JOptionPane.NO_OPTION) {
						// try to load the character
						String pathStr = chooseCharacterFolder();
						if (pathStr != null) {
							Path path = FileSystems.getDefault().getPath(pathStr);
							if (load(path)) {
								MainPanel.this.editor.characterPath = path;
							}
						}
					} else if (result == JOptionPane.YES_OPTION) {
						boolean saved = false;
						if (MainPanel.this.editor.characterPath == null) {
							String pathStr = chooseCharacterFolder();
							if (pathStr != null) {
								// try saving
								Path path = FileSystems.getDefault().getPath(pathStr);
								saved = save(path);
							}
						} else {
							// try saving
							saved = save(MainPanel.this.editor.characterPath);
						}
						if (saved) {
							// try to load the character
							String pathStr = chooseCharacterFolder();
							if (pathStr != null) {
								Path path = FileSystems.getDefault().getPath(pathStr);
								if (load(path)) {
									MainPanel.this.editor.characterPath = path;
								}
							}
						}
					}
				}
			}
		});

		JMenuItem saveAsMenuItem = new JMenuItem("Save character as...");
		fileMenu.add(saveAsMenuItem);
		saveAsMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (MainPanel.this.editor) {
					String pathStr = chooseCharacterFolder();
					if (pathStr != null) {
						// try saving
						Path path = FileSystems.getDefault().getPath(pathStr);
						if (save(path)) {
							MainPanel.this.editor.characterPath = path;
						}
					}
				}
			}
		});

		JMenuItem saveMenuItem = new JMenuItem("Save character");
		saveMenuItem.setMnemonic(KeyEvent.VK_S);
		saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
		fileMenu.add(saveMenuItem);
		saveMenuItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized (MainPanel.this.editor) {
					if (MainPanel.this.editor.characterPath == null) {
						String pathStr = chooseCharacterFolder();
						if (pathStr != null) {
							// try saving
							Path path = FileSystems.getDefault().getPath(pathStr);
							if (save(path)) {
								MainPanel.this.editor.characterPath = path;
							}
						}
					} else {
						// try saving
						save(MainPanel.this.editor.characterPath);
					}
				}
			}
		});

		setJMenuBar(menuBar);

		// editor tabs
		tabs = new JTabbedPane();

		panels = new EditorPanel[] {
				new SkeletonPanel(editor),
				new SkinPanel(editor),
				new AnimationPanel(editor),
				new AnimationTreePanel(editor),
				new AnimationStateMachinePanel(editor)
		};

		for (EditorPanel panel : panels) {
			tabs.add(panel.getTabName(), panel);
		}

		tabs.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				synchronized (MainPanel.this.editor) {
					JTabbedPane tabs = (JTabbedPane) e.getSource();
					selectPanel(tabs.getSelectedIndex());
				}
			}
		});

		add(tabs);

		synchronized (this.editor) {
			startNewCharacter();
			selectedPanel = 0;
			panels[selectedPanel].select();
		}

		addWindowListener(new WindowListener() {
			public void windowClosed(WindowEvent e) {
				for (EditorPanel panel : panels) {
					panel.close();
				}
			}

			public void windowClosing(WindowEvent e) {}
			public void windowOpened(WindowEvent e) {}
			public void windowIconified(WindowEvent e) {}
			public void windowDeiconified(WindowEvent e) {}
			public void windowActivated(WindowEvent e) {}
			public void windowDeactivated(WindowEvent e) {}
		});

		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		setVisible(true);
	}

	private String chooseCharacterFolder() {
		JFileChooser chooser = new JFileChooser();
		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int result = chooser.showOpenDialog(this);
		if (result == JFileChooser.APPROVE_OPTION) {
			return chooser.getSelectedFile().getAbsolutePath();
		} else {
			return null;
		}
	}

	private boolean save(Path path) {
		Path temp = path.resolve("temp");
		try {
			try {
				Files.createDirectory(temp);
			} catch (IOException e) {
				throw new IOException("Failed to create temp save directory");
			}

			editor.skeleton.save(temp.resolve("skeleton.skel"));
			editor.animationTree.save(temp.resolve("animationTree.tree"));
			editor.animationStateMachine.save(temp.resolve("animationStateMachine.sm"));
			for (int i = 0; i < editor.animations.size(); ++i) {
				editor.animations.get(i).save(temp.resolve(editor.animationNames.get(i) + ".anim"));
			}

			editor.skinComponentCollection.save(temp.resolve("skinComponentCollection.scc"));
			for (int i = 0; i < editor.skins.size(); ++i) {
				editor.skins.get(i).save(temp.resolve(editor.skinNames.get(i) + ".skin"));
			}
			if (editor.skinImage != null) {
				Path out = temp.resolve("skin.png");
				ImageIO.write(editor.skinImage, "png", out.toFile());
			}

			// now delete contents of file and move temp to file
			// todo: File isn't deprecated but maybe find a way to switch to using only Path
			File[] files = path.toFile().listFiles();
			if (files != null) {
				for (File f : files) {
					// remove everything except for the temp folder
					if (!f.isDirectory()) {
						f.delete();
					}
				}
			}

			// copy all files from temp
			files = temp.toFile().listFiles();
			if (files != null) {
				for (File f : files) {
					File dest = new File(f.getParentFile().getParentFile().getPath() + File.separator + f.getName());
					Files.move(f.toPath(), dest.toPath());
				}
			}

			temp.toFile().delete();
			return true;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			System.out.println("Failed to save");
			return false;
		}
	}

	private boolean load(Path path) {
		try {
			editor.skeleton.load(path.resolve("skeleton.skel"));
			editor.animationTree.load(path.resolve("animationTree.tree"));
			editor.animationStateMachine.load(path.resolve("animationStateMachine.sm"));
			editor.animations.clear();
			editor.animationNames.clear();
			File[] animFiles = path.toFile().listFiles(new java.io.FileFilter() {
				@Override
				public boolean accept(File pathname) {
					return !pathname.isDirectory() && pathname.getName().toLowerCase().endsWith(".anim");
				}
			});
			for (File animFile : animFiles) {
				// remove extension from name
				editor.animationNames.add(animFile.getName().substring(0, animFile.getName().lastIndexOf('.')));
				Animation animation = new Animation();
				animation.load(path.resolve(animFile.getName()));
				editor.animations.add(animation);
			}

			editor.skinComponentCollection.load(path.resolve("skinComponentCollection.scc"));
			editor.skins.clear();
			editor.skinNames.clear();
			File[] skinFiles = path.toFile().listFiles(new java.io.FileFilter() {
				@Override
				public boolean accept(File pathname) {
					return !pathname.isDirectory() && pathname.getName().toLowerCase().endsWith(".skin");
				}
			});
			for (File skinFile : skinFiles) {
				// remove extension from name
				editor.skinNames.add(skinFile.getName().substring(0, skinFile.getName().lastIndexOf('.')));
				Skin skin = new Skin();
				skin.load(path.resolve(skinFile.getName()));
				editor.skins.add(skin);
			}

			editor.skinImage = null;
			Path skinTexturePath = path.resolve("skin.png");
			if (skinTexturePath.toFile().exists()) {
				editor.skinImage = ImageIO.read(skinTexturePath.toFile());
			}
			editor.refreshSkinImage = true;

			selectPanel(tabs.getSelectedIndex());
			return true;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			System.out.println("Failed to load");
			startNewCharacter();
			return false;
		}
	}

	private void startNewCharacter() {
		editor.characterPath = null;
		editor.skeleton = new Skeleton();
		editor.animations.clear();
		editor.animationNames.clear();
		editor.animationTree = new AnimationTree();
		AnimationTree.AnimationNode root = new AnimationTree.AnimationNode("root");
		root.setAnimation("");
		editor.animationTree.setRoot(root);
		editor.animationStateMachine = new AnimationStateMachine();

		editor.skinComponentCollection = new SkinComponentCollection();
		editor.skins.clear();
		editor.skinNames.clear();
		editor.skinImage = null;
		editor.refreshSkinImage = true;

		selectPanel(tabs.getSelectedIndex());
	}

	private int selectedPanel;
	private void selectPanel(int i) {
		panels[selectedPanel].deselect();
		selectedPanel = i;
		panels[selectedPanel].select();
	}

	// preview functions
	public void selectBoneInPreview(int boneIndex) {
		panels[tabs.getSelectedIndex()].selectBoneInPreview(boneIndex);
	}

	public void translateInPreview(Vector2f translation) {
		panels[tabs.getSelectedIndex()].translateInPreview(translation);
	}

	public void finishTranslateInPreview() {
		panels[tabs.getSelectedIndex()].finishTranslateInPreview();
	}

	public void rotateInPreview(float rotation) {
		panels[tabs.getSelectedIndex()].rotateInPreview(rotation);
	}

	public void finishRotateInPreview() {
		panels[tabs.getSelectedIndex()].finishRotateInPreview();
	}

	public void scaleInPreview(Vector2f scale) {
		panels[tabs.getSelectedIndex()].scaleInPreview(scale);
	}

	public void finishScaleInPreview() {
		panels[tabs.getSelectedIndex()].finishScaleInPreview();
	}

	public Vector2f getBoneTranslation(int boneIndex) {
		return panels[tabs.getSelectedIndex()].getBoneTranslation(boneIndex);
	}

	public float getBoneRotation(int boneIndex) {
		return panels[tabs.getSelectedIndex()].getBoneRotation(boneIndex);
	}

	public Vector2f getBoneScale(int boneIndex) {
		return panels[tabs.getSelectedIndex()].getBoneScale(boneIndex);
	}

	public void enableBoneInAnimationInPreview() {
		panels[tabs.getSelectedIndex()].enableBoneInAnimationInPreview();
	}

	public void addKeyframeInPreview() {
		panels[tabs.getSelectedIndex()].addKeyframeInPreview();
	}

	public void prevKeyframeInPreview(boolean first) {
		panels[tabs.getSelectedIndex()].prevKeyframeInPreview(first);
	}

	public void nextKeyframeInPreview(boolean last) {
		panels[tabs.getSelectedIndex()].nextKeyframeInPreview(last);
	}

	public void frameBeginInPreview() {
		panels[tabs.getSelectedIndex()].frameBeginInPreview();
	}
}
